#include <setjmp.h>
#include "Scanner.h"
#include "parser.h"

static jmp_buf bail;

static int scanner_next(int* s)
{
	return tokenType[(*s)++];
}

int find_ref(const char* name, Scope* scope, int* ll, Ref** ref)
{
	int i;
	if(ll)
		*ll = 0;
	while(scope)
	{
		for(i = 0; i < scope->nRef; i++)
		{
			if(strcmp(name, scope->refs[i].name) == 0)
			{
				if(ref)
					*ref = scope->refs + i;
				return 1;
			}
		}
		scope = scope->parent;
	}
	return 0;
}

void add_ref(Scope* scope, Ref* src)
{
	if(!src->isConst)
	{
		src->index = scope->space;
		scope->space += src->arraySize;
	}
	scope->nRef++;
	scope->refs = realloc(scope->refs, sizeof(Ref) * scope->nRef);
	memcpy(scope->refs + scope->nRef - 1, src, sizeof(Ref));
}

const char* symname(int sym)
{
	switch(sym)
	{
		case nulsym: return "nulsym";
		case identsym: return "identsym";
		case numbersym: return "numbersym";
		case plussym: return "plussym";
		case minussym: return "minussym";
		case multsym: return "multsym";
		case slashsym: return "slashsym";
		case oddsym: return "oddsym";
		case eqsym: return "eqsym";
		case neqsym: return "neqsym";
		case lessym: return "lessym";
		case leqsym: return "leqsym";
		case gtrsym: return "gtrsym";
		case geqsym: return "geqsym";
		case lparensym: return "lparensym";
		case rparensym: return "rparensym";
		case commasym: return "commasym";
		case semicolonsym: return "semicolonsym";
		case periodsym: return "periodsym";
		case becomessym: return "becomessym";
		case beginsym: return "beginsym";
		case endsym: return "endsym";
		case ifsym: return "ifsym";
		case thensym: return "thensym";
		case whilesym: return "whilesym";
		case dosym: return "dosym";
		case callsym: return "callsym";
		case constsym: return "constsym";
		case intsym: return "intsym";
		case procsym: return "procsym";
		case writesym: return "writesym";
		case lbracketsym: return "lbracketsym";
		case rbracketsym: return "rbracketsym";
	}
	return "unknown";
}

static void expect(int* s, int sym)
{
	int next = scanner_next(s);
	if(next != sym)
	{
		fprintf(stderr, "%s expected, got %s.\n", symname(sym), symname(next));
		longjmp(bail, 1);
	}
}

static void emit_instruction(int* pc, int op, int ll, int method)
{
	code[*pc].op = op;
	code[*pc].l = ll;
	code[*pc].m = method;
	(*pc)++;
}

static void parse_expression(int* s, int* program, Scope* scope);

static void parse_factor(int* s, int* program, Scope* scope)
{
	int ll, sym;
	Ref* rp;
	sym = scanner_next(s);
	if(sym == identsym)
	{
		if(!find_ref(tokens[*s - 1], scope, &ll, &rp))
		{
			fprintf(stderr, "%s undeclared.\n", tokens[*s - 1]);
			longjmp(bail, 1);
		}
		sym = scanner_next(s);
		if(sym == lbracketsym) {
			if(rp->isConst) {
				fprintf(stderr, "Cannot use %s as array.\n", rp->name);
				longjmp(bail, 1);
			}
			emit_instruction(program, INC, 0, 1);
			emit_instruction(program, CAL, 0, *program + 1);
			emit_instruction(program, INC, 0, 3);
			parse_expression(s, program, scope);
			emit_instruction(program, LOD, 0, 1);
			emit_instruction(program, STO, 0, 0);
			emit_instruction(program, LOD, 0, 1);
			emit_instruction(program, OPR, 0, ADD);
			emit_instruction(program, STO, 0, 1);
			emit_instruction(program, LOD, 1, rp->index + 3);
			emit_instruction(program, STO, 0, -1);
			emit_instruction(program, LOD, 0, 0);
			emit_instruction(program, STO, 0, 1);
			emit_instruction(program, LIT, 0, *program + 3);
			emit_instruction(program, STO, 0, 2);
			emit_instruction(program, OPR, 0, RET);
			expect(s, rbracketsym);
		}
		else {
			(*s)--;
			if(rp->isConst)
				emit_instruction(program, LIT, 0, rp->index);
			else
				emit_instruction(program, LOD, ll, rp->index + 3);
		}
	}
	else if(sym == numbersym)
	{	
		emit_instruction(program, LIT, 0, atoi(tokens[*s - 1]));
	}
	else if(sym == lparensym)
	{
		parse_expression(s, program, scope);
		expect(s, rparensym);
	}
	else
	{	
		fprintf(stderr, "Invalid %s in expression.\n", symname(sym));
		longjmp(bail, 1);
	}
}

static void parse_term(int* s, int* program, Scope* scope)
{
	int sym = intsym;
	
	parse_factor(s, program, scope);
	
	while(1)
	{
		sym = scanner_next(s);
		if(sym == multsym || sym == slashsym)
		{
			parse_factor(s, program, scope);
			emit_instruction(program, OPR, 0, sym == multsym ? MUL : DIV);
		}
		else
		{
			(*s)--;
			break;
		}
	}
}

static void parse_expression(int* s, int* program, Scope* scope)
{
	int sym = intsym;
	int sign = 1, first = 1;
	
	sym = scanner_next(s);
	while(1)
	{
		if(sym == plussym)
			sign = 1;
		else if(sym == minussym)
			sign = -1;
		else
		{
			(*s)--;
			if(!first)
				break;
		}
		parse_term(s, program, scope);
		if(first)
		{
			if(sign == -1)
				emit_instruction(program, OPR, 0, NEG);
		}
		else
			emit_instruction(program, OPR, 0, sign == 1 ? ADD : SUB);
		first = 0;
		sym = scanner_next(s);
	}
}

static void parse_condition(int* s, int* program, Scope* scope)
{
	int sym;
	sym = scanner_next(s);
	if(sym == oddsym)
	{
		parse_expression(s, program, scope);
		emit_instruction(program, OPR, 0, ODD);
	}
	else
	{
		(*s)--;
		parse_expression(s, program, scope);
		sym = scanner_next(s);
		if(sym != eqsym && sym != neqsym && sym != lessym && sym != leqsym && sym != gtrsym && sym != geqsym)
		{
			fprintf(stderr, "Expected relational operator, found %s.\n", symname(sym));
			longjmp(bail, 1);
		}
		parse_expression(s, program, scope);
		switch(sym)
		{
			case eqsym: sym = EQL; break;
			case neqsym: sym = NEQ; break;
			case lessym: sym = LSS; break;
			case leqsym: sym = LEQ; break;
			case gtrsym: sym = GTR; break;
			case geqsym: sym = GEQ; break;
		}
		emit_instruction(program, OPR, 0, sym);
	}
}

static void parse_const(int* s, int* program, Scope* scope)
{
	int ll, sym;
	Ref r = { "", 0, 0 }, *rp;
	sym = scanner_next(s);
	if(sym != constsym)
	{
		(*s)--;
		return;
	}
	while(1)
	{
		expect(s, identsym);
		if(find_ref(tokens[*s - 1], scope, &ll, &rp) && ll == 0)
		{
			fprintf(stderr, "%s redeclared.\n", tokens[*s - 1]);
			longjmp(bail, 1);
		}	
		strcpy(r.name, tokens[*s - 1]);
		r.isConst = 1;
		expect(s, eqsym);
		expect(s, numbersym);
		r.index = atoi(tokens[*s - 1]);
		add_ref(scope, &r);
		sym = scanner_next(s);
		if(sym != commasym)
		{
			(*s)--;
			break;
		}
	}
	expect(s, semicolonsym);
}

static void parse_vars(int* s, int* program, Scope* scope)
{
	int ll, sym;
	Ref r = { "", 0, 0 }, *rp;
	sym = scanner_next(s);
	if(sym != intsym)
	{
		(*s)--;
		return;
	}
	while(1)
	{
		expect(s, identsym);
		if(find_ref(tokens[*s - 1], scope, &ll, &rp) && ll == 0)
		{
			fprintf(stderr, "%s redeclared.\n", tokens[*s - 1]);
			longjmp(bail, 1);
		}	
		strcpy(r.name, tokens[*s - 1]);
		r.arraySize = 1;
		sym = scanner_next(s);
		if(sym == lbracketsym)
		{
			expect(s, numbersym);
			r.arraySize = atoi(tokens[*s - 1]);
			if(r.arraySize < 1) {
				fprintf(stderr, "Array %s has size less than 1.\n", r.name);
				longjmp(bail, 1);
			}
			expect(s, rbracketsym);
			sym = scanner_next(s);
		}
		add_ref(scope, &r);
		if(sym != commasym)
		{
			(*s)--;
			break;
		}
	}
	expect(s, semicolonsym);
}

static void parse_statement(int* s, int* program, Scope* scope)
{
	int ll, sym;
	Ref* rp;
	sym = scanner_next(s);
	if(sym == identsym)
	{
		if(!find_ref(tokens[*s - 1], scope, &ll, &rp))
		{
			fprintf(stderr, "%s undeclared.\n", tokens[*s - 1]);
			longjmp(bail, 1);
		}
		if(rp->isConst)
		{
			fprintf(stderr, "Cannot reassign constant %s.\n", tokens[*s - 1]);
			longjmp(bail, 1);
		}
		sym = scanner_next(s);
		if(sym == lbracketsym) {
			int old_s, old_program;
			old_s = *s;
			old_program = *program;
			parse_expression(s, &old_program, scope);
			expect(s, rbracketsym);
			expect(s, becomessym);
			parse_expression(s, program, scope);
			parse_expression(&old_s, program, scope);
			emit_instruction(program, CAL, 0, *program + 1);
			emit_instruction(program, INC, 0, 3);
			emit_instruction(program, LOD, 0, -2);
			emit_instruction(program, LOD, 0, -1);
			emit_instruction(program, LOD, 0, 1);
			emit_instruction(program, STO, 0, 0);
			emit_instruction(program, LOD, 0, 1);
			emit_instruction(program, OPR, 0, ADD);
			emit_instruction(program, STO, 0, 1);
			emit_instruction(program, STO, 1, rp->index + 3);
			emit_instruction(program, STO, 0, 2);
			emit_instruction(program, LOD, 0, 0);
			emit_instruction(program, STO, 0, 1);
			emit_instruction(program, LIT, 0, *program + 2);
			emit_instruction(program, OPR, 0, RET);
			emit_instruction(program, INC, 0, -2);
		}
		else {
			(*s)--;
			expect(s, becomessym);
			parse_expression(s, program, scope);
			emit_instruction(program, STO, ll, rp->index + 3);
		}
	}
	else if(sym == beginsym)
	{
		while(1)
		{
			parse_statement(s, program, scope);
			sym = scanner_next(s);
			if(sym != semicolonsym)
			{
				(*s)--;
				break;
			}
		}
		sym = scanner_next(s);
		if(sym != endsym)
		{
			fprintf(stderr, "Expected %s or %s, found %s.\n", symname(semicolonsym), symname(endsym), symname(sym));
			longjmp(bail, 1);
		}
	}
	else if(sym == ifsym)
	{
		parse_condition(s, program, scope);
		expect(s, thensym);
		int jpc = *program;
		emit_instruction(program, JPC, 0, 0);
		parse_statement(s, program, scope);
		emit_instruction(&jpc, JPC, 0, *program);
	}
	else
	{
		(*s)--;
	}
}

static void parse_block(int* s, int* program, Scope* parent)
{
	Scope* scope;
	scope = malloc(sizeof(Scope));
	memset(scope, 0, sizeof(Scope));
	scope->parent = parent;
	
	parse_const(s, program, scope);
	parse_vars(s, program, scope);
	emit_instruction(program, INC, 0, scope->space + 3);
	parse_statement(s, program, scope);
}

int parse_program()
{
	int scanner_pos = 0, program = 0;
	
	if(setjmp(bail) != 0)
		return 0;
		
	memset(code, -1, sizeof(code));
	
	parse_block(&scanner_pos, &program, NULL);
	expect(&scanner_pos, periodsym);
	
	return 1;
}

